package top.v5it.japi.ddd.core.exception;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ReflectUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import top.v5it.japi.ddd.core.entity.ErrorResult;
import top.v5it.japi.ddd.core.entity.ResultStatus;
import top.v5it.japi.ddd.core.entity.ResultStatusTemplate;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Collectors;


/**
 * 全局响应异常处理
 *
 * @author zhanpu
 */
@Slf4j
@ControllerAdvice
public class GlobalResponseExceptionHandler {

    /**
     * 响应headers字段值
     */
    private static final String CACHE_CONTROL_VALUE = "no-store";

    /**
     * 响应headers字段值
     */
    private static final String PRAGMA_VALUE = "no-cache";

    /**
     * 获取请求uri
     */
    private static final String PATH = "org.springframework.web.servlet.HandlerMapping.pathWithinHandlerMapping";

    /**
     * 统一错误异常处理
     *
     * @param response {@link HttpServletResponse}
     * @param ex       具体异常类
     * @return {@link Map <String, Object>}
     * @throws Exception 方法本身也可能抛出异常
     */
    @ExceptionHandler(Exception.class)
    @ResponseBody
    public ErrorResult resolveException(HttpServletRequest request, HttpServletResponse response, Exception ex) {
        ResultStatusTemplate template;
        if (ex instanceof NotFoundException && ex.getClass().isAssignableFrom(NotFoundException.class)) {
            template = ResultStatus.NOT_FOUND;
        } else {
            // 解析异常类中是否带有 ResultStatusTemplate 属性，有并解析
            template = Arrays.stream(ReflectUtil.getFieldsValue(ex))
                    .filter(v -> v instanceof ResultStatusTemplate && v.getClass().isAssignableFrom(ResultStatusTemplate.class))
                    .map(ResultStatusTemplate.class::cast)
                    .findFirst()
                    .orElse(ResultStatus.SYSTEM_ERROR);
        }
        return createReturnError(request, response, template, ex.getMessage());
    }

    /**
     * 统一捕获处理IllegalArgumentException异常
     */
    @ExceptionHandler(IllegalArgumentException.class)
    @ResponseBody
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ErrorResult resolveIllegalArgumentException(HttpServletRequest request, HttpServletResponse response, IllegalArgumentException ex) {
        return createReturnError(request, response, ResultStatus.PARAM_ERROR, ex.getMessage());
    }

    /**
     * 统一捕获处理ConstraintViolationException异常
     */
    @ExceptionHandler(ConstraintViolationException.class)
    @ResponseBody
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ErrorResult resolveConstraintViolationException(HttpServletRequest request, HttpServletResponse response, ConstraintViolationException ex) {
        final String message = resolveErrorMessage(ex.getConstraintViolations(), ConstraintViolation::getMessage);
        return createReturnError(request, response, ResultStatus.PARAM_ERROR, message);
    }

    /**
     * 统一捕获处理MethodArgumentNotValidException异常
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseBody
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ErrorResult resolveMethodArgumentNotValidException(HttpServletRequest request, HttpServletResponse response, MethodArgumentNotValidException ex) {
        final String message = resolveErrorMessage(ex.getBindingResult().getAllErrors(), DefaultMessageSourceResolvable::getDefaultMessage);
        return createReturnError(request, response, ResultStatus.PARAM_ERROR, message);
    }

    /**
     * 解析错误消息
     *
     * @param coll 异常集合
     * @param func 转换函数
     * @param <T>  异常类型
     * @return 异常说明
     */
    private <T> String resolveErrorMessage(Collection<? extends T> coll, Function<T, String> func) {
        if (CollUtil.isEmpty(coll)) return ResultStatus.PARAM_ERROR.getMessage();
        return coll.stream().map(func).collect(Collectors.joining(";"));
    }


    /**
     * 创建返回信息
     *
     * @param response {@link HttpServletResponse}
     * @param template 响应结果
     * @param message  描述
     * @return {@link ErrorResult}
     */
    private ErrorResult createReturnError(HttpServletRequest request, HttpServletResponse response, ResultStatusTemplate template, final String message) {
        response.setHeader(HttpHeaders.CACHE_CONTROL, CACHE_CONTROL_VALUE);
        response.setHeader(HttpHeaders.PRAGMA, PRAGMA_VALUE);
        response.setStatus(template.getStatus());
        final Object path = request.getAttribute(PATH);
        return ErrorResult.failure(template, message, Objects.toString(path));
    }

}
